package com.gmrz.uaf.protocol.v1.processor;

import com.gmrz.uaf.common.Constants;
import com.gmrz.uaf.common.UAFRuntimeException;
import com.gmrz.uaf.crypto.AlgorithmNotFoundException;
import com.gmrz.uaf.crypto.CryptoEngine;
import com.gmrz.uaf.crypto.spi.UAFAuthAlgorithmSuite;
import com.gmrz.uaf.db.DAOException;
import com.gmrz.uaf.db.UAFDAOFactory;
import com.gmrz.uaf.db.UAFDBConnectionMgr;
import com.gmrz.uaf.db.dao.AuthenticatorDAO;
import com.gmrz.uaf.png.TextToGraphics;
import com.gmrz.uaf.protocol.v1.processor.exception.TxnContentCreationException;
import com.gmrz.uaf.protocol.v1.processor.exception.UAFErrorCode;
import com.gmrz.uaf.protocol.v1.schema.Authenticator;
import com.gmrz.uaf.protocol.v1.schema.PNGCharacteristics;
import com.gmrz.uaf.protocol.v1.schema.ServerData;
import com.gmrz.uaf.protocol.v1.schema.Transaction;
import com.gmrz.util.Convert;
import com.gmrz.util.db.DBUtil;
import com.google.common.base.Strings;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.*;

public class TransactionContentProcessor {
	private static final Logger LOG = LogManager
			.getLogger(TransactionContentProcessor.class);
	private static final int TXN_TEXT_PLAIN_LENGTH = 200;

	public static List<Transaction> create(String username,
			UAFDBConnectionMgr dbutil, String text, ServerData serverData,
			CryptoEngine cryptoEngine,String appID,String deviceID,String authType,String transType) throws DAOException, IOException,
			AlgorithmNotFoundException,
			TxnContentCreationException, GeneralSecurityException {
		List<Transaction> retval = new ArrayList<Transaction>();
		Connection connection = null;
		Set<Authenticator> registrations = null;
		HashSet<String> hashData = new HashSet<String>();

		byte[] decodedTextBytes = Convert.fromBase64(text);
		String decodedTextString = Convert.fromUtf8(decodedTextBytes);
		LOG.info("Transaction text decoded from Base64[{}]",
				decodedTextString);
		// 如果hashUserName是的空的话就使用默认的 -1 算法套件
		if (Strings.isNullOrEmpty(username)) {
			processTextContentType(retval, hashData, serverData, -1,
					decodedTextBytes, cryptoEngine);
			return retval;
		}
		try {
			connection = UAFDBConnectionMgr.getConnection(true);
//			AuthenticatorDAO dao = GuiceUtil
//					.getProcessorInjector().getInstance(DAOFactory.class)
//					.createAuthenticatorDAO(connection);
			AuthenticatorDAO dao = UAFDAOFactory.createAuthenticatorDAO(connection);
			// 去认证数据表中查询已经注册的认证数据
			registrations = dao.findUserOnDevice(username,appID,deviceID,authType,transType,
					Constants.AuthenticatorStatus.ACTIVE.getStatus());
		} catch (SQLException sqle) {
			throw new DAOException(UAFErrorCode.DB_DATABASE_CONNECTION_ERROR, sqle);
		} finally {
			DBUtil.close(connection);
		}

		Authenticator reg;
		for (Iterator<Authenticator> i$ = registrations.iterator(); i$.hasNext();) {
			reg = i$.next();
			List<String> ctypes = new ArrayList<String>();
			ctypes.add(reg.getMetadata().getDisplayContentType());
			List<String> contentTypes = ctypes;
			for (String ctype : contentTypes) {
				Constants.ContentType contentType = Constants.ContentType
						.forName(ctype);
				switch (contentType) {
				case TEXT:
					// 如果交易内容的类是文本的话，则从对应的认证数据中的元数据中获取算法套件标识
					int algoSuite = reg.getMetadata().getAlgorithm();
					processTextContentType(retval, hashData, serverData,
							algoSuite, decodedTextBytes, cryptoEngine);
					break;
				case PNG:
					processPNGContentType(serverData, cryptoEngine, retval,
							hashData, decodedTextString, reg, contentType);
					break;
				case JSON:
				default:
					LOG.error("Unsupported content type :"
							+ contentType.getMimeType());
					throw new UAFRuntimeException("Unsupported content type :"
							+ contentType.getMimeType());
				}
			}
		}
		return retval;
	}

	private static void processPNGContentType(ServerData serverData,
			CryptoEngine cryptoEngine, List<Transaction> retval,
			HashSet<String> hashData, String decodedTextString,
			Authenticator reg, Constants.ContentType contentType)
			throws TxnContentCreationException, IOException,
			AlgorithmNotFoundException, GeneralSecurityException {
		List<PNGCharacteristics> pngCharacteristics = reg
				.getPngCharacteristics();
		pngCharacteristics = (pngCharacteristics == null)
				|| (pngCharacteristics.isEmpty()) ? reg.getMetadata()
				.getDisplayPNGCharacteristics() : pngCharacteristics;

		if ((pngCharacteristics == null) || (pngCharacteristics.isEmpty())) {
			LOG.error(
					"PNG Characteristics for authenticator[{}] are required to support this transaction.",
					reg.getAAID());

			throw new TxnContentCreationException(
					UAFErrorCode.PROTOCOL_TXN_DISPLAY_INFO_NOT_FOUND);
		}

		for (PNGCharacteristics pnginfo : pngCharacteristics) {
			try {
				processPNGCharacteristics(serverData, cryptoEngine, retval,
						hashData, decodedTextString, reg, contentType, pnginfo);
			} catch (Exception e) {
				LOG.error("Failed to create the transaction content from [{}]",
						pnginfo, e);
			}
		}

		if (retval.isEmpty())
			throw new TxnContentCreationException(
					UAFErrorCode.PROTOCOL_TXN_CONTENT_CREATE_FAILED);
	}

	private static void processPNGCharacteristics(ServerData serverData,
			CryptoEngine cryptoEngine, List<Transaction> retval,
			HashSet<String> hashData, String decodedTextString,
			Authenticator reg, Constants.ContentType contentType,
			PNGCharacteristics pnginfo) throws IOException,
			AlgorithmNotFoundException, GeneralSecurityException {
		
		TextToGraphics g = new TextToGraphics(pnginfo.width, pnginfo.height,
				pnginfo.bitDepth, pnginfo.colorType, readPalette(pnginfo));

		byte[] rawImage = g.convert2PNG(decodedTextString);
		boolean pngadded = false;

		int algoSuite = reg.getMetadata().getAlgorithm();

		UAFAuthAlgorithmSuite suite = cryptoEngine
				.getAuthAlgorithmSuite(algoSuite);
		byte[] rawHash = suite.hash(rawImage);
		String b64Hash = Convert.toBase64(rawHash);
		String b64Data = Convert.toBase64(rawImage);
		if (!hashData.contains(b64Hash)) {
			if (!pngadded) {
				Transaction t = new Transaction();
				t.withContent(b64Data)
						.withContentType(contentType.getMimeType())
						.withPNGDescriptor(pnginfo);
				retval.add(t);
				pngadded = true;
			}
			hashData.add(b64Hash);
			serverData.addTransactionHash(b64Hash);
		}
	}

	private static byte[][] readPalette(PNGCharacteristics info) {
		if ((info.pallets == null) || (info.pallets.length == 0))
			return null;
		byte[][] retval = new byte[3][info.pallets.length];
		for (int i = 0; i < info.pallets.length; i++) {
			retval[0][i] = ((byte) info.pallets[i].r);
			retval[1][i] = ((byte) info.pallets[i].g);
			retval[2][i] = ((byte) info.pallets[i].b);
		}

		return retval;
	}

	public static boolean verify(String transactionHash, ServerData serverData) {
		Set<String> cachedHash = serverData.getTransactionHashSet();
		return cachedHash.contains(transactionHash);
	}

	static void processTextContentType(List<Transaction> retval,
			HashSet<String> hashData, ServerData serverData, int algoSuite,
			byte[] decodedTextBytes, CryptoEngine cryptoEngine)
			throws AlgorithmNotFoundException,
			TxnContentCreationException, GeneralSecurityException {
		if (decodedTextBytes.length > TXN_TEXT_PLAIN_LENGTH) {
			LOG.error(
					"Max length allowed for contentType text/plain[{}], actual[{}]",
					Integer.valueOf(TXN_TEXT_PLAIN_LENGTH),
					Integer.valueOf(decodedTextBytes.length));

			throw new TxnContentCreationException(
					UAFErrorCode.PROTOCOL_TXN_CONTENT_CREATE_FAILED);
		}
		byte[] rawHash = null;
		if (algoSuite == -1) {
			rawHash = cryptoEngine.getDataHashHandler().hash(decodedTextBytes);
		} else {
			// 根据算法套件获取对应的 hash算法
			UAFAuthAlgorithmSuite suite = cryptoEngine
					.getAuthAlgorithmSuite(algoSuite);
			// 使用hash算法对交易内容进行hash
			rawHash = suite.hash(decodedTextBytes);
		}
		// 对 hash 后的交易内容进行 base64编码
		String b64Hash = Convert.toBase64(rawHash);
		if (!hashData.contains(b64Hash)) {
			String encodedText = Convert.toBase64(decodedTextBytes);
			Transaction t = new Transaction();
			t.withContent(encodedText).withContentType(
					Constants.ContentType.TEXT.getMimeType());

			retval.add(t);
			hashData.add(b64Hash);
			// 放到serverdata中
			serverData.addTransactionHash(b64Hash);
		}
	}
}
